//#include "HVisitor.h"
//#include <cassert>
#include "HExceptions.h"
//#include "HSymbolTable.h"
//#include "HFunctionTable.h"
#include "HIRCode.h"
#include "HWarnings.h"

using namespace antlr4;

#define CHECK_EXCEPTION(ctx)				\
	if (ctx->exception)	{					\
		error_code = int(HECode::STXERR);	\
		return nullptr;						\
	}

/*
 * NOTE: to protect our global variables, we use `static`.
 * If they should be accessed in other source file later,
 * remove the `static` and add a declaration in `HVisitor.h`.
 */
// The current symbol table while visiting the parse tree
static HSymbolTable *cur_symtb;
// The global function table
HFunctionTable functable;
// The global IR code table
HIRCode hxd1_ir;
// The global warning table
HWarnings warnings;

// The return type of current function, or `UNKNOWN` if not in function
static Btype cur_func_rtype = Btype::UNKNOWN;

static inline bool is_bool_scalar(const cact_type_t &t)
{
	return (t.base_type == Btype::BOOL && t.dim.size() == 0UL);
}

// Get the base type according to context `bctx`, return `UNKNOWN` on error
static Btype get_b_type(CACTParser::B_typeContext *bctx)
{
	if (bctx->INT())
		return Btype::INT;
	else if (bctx->BOOL())
		return Btype::BOOL;
	else if (bctx->FLOAT())
		return Btype::FLOAT;
	else if (bctx->DOUBLE())
		return Btype::DOUBLE;
	else	// error
		return Btype::UNKNOWN;
}

// Get the base type according to context `fctx`, return `UNKNOWN` on error
static Btype get_f_type(CACTParser::Func_typeContext *fctx)
{
	if (fctx->INT())
		return Btype::INT;
	else if (fctx->BOOL())
		return Btype::BOOL;
	else if (fctx->FLOAT())
		return Btype::FLOAT;
	else if (fctx->DOUBLE())
		return Btype::DOUBLE;
	else if (fctx->VOID())
		return Btype::VOID;
	else	// error
		return Btype::UNKNOWN;
}

/*
 * Cast string `str` to integer.
 * DEC, HEX, OCT are supported, but signal is not.
 * NOTE: exception may be thrown in the situation such as
 * out of range!
 */
static long get_int_const(const std::string &str)
{
	size_t i = 0U;
	while (isspace(str[i]))
		++i;
	if (str[i] != '0')	// DEC
		return std::stoll(str);
	else if (str.length() > i + 1UL &&
		(str[i + 1U] == 'x' || str[i + 1U] == 'X'))	// HEX
		return std::stoll(str.substr(i + 2U), nullptr, 16);
	else	// OCT
		return std::stoll(str, nullptr, 8);
}

/*
 * Determine whether const initial value `src` can be used to
 * initialize a variable of type `dst`.
 */
static bool is_valid_init_val(const cact_type_t &dst, const cact_type_t &src)
{
	size_t sdepth = src.dim.size(), ddepth = dst.dim.size();
	uint64_t dsize = 1UL;
	if (src.base_type == Btype::UNKNOWN)
		return false;
	if (!src.is_const)
		return false;
	if (src.base_type != Btype::VOID && src.base_type != dst.base_type)
		return false;
	// Calc the number of elements of `dst`
	for (auto n : dst.dim)
		dsize *= n;
	// Allow flat pattern `int K[18][06] = {7,5,0,3,8};`
	if (sdepth == 1UL && ddepth > 0UL && src.dim[0] <= dsize)
		return true;

	if (sdepth != ddepth)
		return false;
	for (size_t i = 0UL; i < ddepth; ++i)
	{
		if (src.dim[i] > dst.dim[i])
			return false;
	}
	return true;
}

/*
 * Determine whether argument with type `atype` is legal for
 * function parameter with type `ptype`.
 */
static bool is_legal_func_arg(const cact_type_t &ptype, const cact_type_t &atype)
{
	size_t pdepth = ptype.dim.size(), adepth = atype.dim.size();

	assert(atype.base_type != Btype::UNKNOWN && atype.base_type != Btype::VOID);
	if (atype.base_type != ptype.base_type)
		return false;
	if (pdepth != adepth)
		return false;
	for (size_t i = 0U; i < pdepth; ++i)
	{
		if (ptype.dim[i] != 0U)
		{
			if(ptype.dim[i] != atype.dim[i])
				return false;
		}
		else
			// only the first dim can be omitted
			assert(i == 0U);
	}
	return true;
}

/*
 * Determine whether the statement terminates with `return`
 * recursively.
 */
static bool is_terminated_with_return(CACTParser::StmtContext *ctx)
{
	if (ctx->RETURN())
		return true;
	if (ctx->IF())
	{
		if (ctx->ELSE())
			return is_terminated_with_return(ctx->stmt(0U)) &&
				is_terminated_with_return(ctx->stmt(1U));
		else
			// `if` without `else`
			return false;
	}
	if (ctx->block())
	{
		CACTParser::StmtContext *last_stmt = nullptr;
		for (auto bi : ctx->block()->block_item())
			if (bi->stmt())
				last_stmt = bi->stmt();
		if (last_stmt == nullptr)
			// a block without any statement
			return false;
		// the last statement in the block
		return is_terminated_with_return(last_stmt);
	}
	// other type of statement (`while`, `expr`, ...)
	return false;
}

/*
 * Structure to maintain a loop stack, recording the `while` loops
 * we have entered to support `break` and `continue`.
 */
struct loop_label {
	HIRLabel begin, next;
};
std::vector<loop_label> loop_stack;
// should be called when we enter a loop
static inline void enter_loop(HIRLabel begin, HIRLabel next)
{
	loop_stack.push_back(loop_label{begin, next});
}
// return the `begin` label of the top or `INVALID_LABEL` if not in a loop
static inline HIRLabel loop_stack_get_top_begin()
{
	size_t s = loop_stack.size();
	if (s > 0ul)
		return loop_stack.at(s - 1ul).begin;
	else
		return INVALID_LABEL;
}
// return the `begin` label of the top or `INVALID_LABEL` if not in a loop
static inline HIRLabel loop_stack_get_top_next()
{
	size_t s = loop_stack.size();
	if (s > 0ul)
		return loop_stack.at(s - 1ul).next;
	else
		return INVALID_LABEL;
}
// should be called when we exit a loop
static inline void exit_loop()
{
	if (loop_stack.size() > 0ul)
		loop_stack.pop_back();
	else
		throw std::logic_error("exiting but not in any loop");
}

std::any HAnalysis::visitComp_unit(CACTParser::Comp_unitContext *ctx)
{
	CHECK_EXCEPTION(ctx);

	// The symbol table tree is not allowed to create two
	assert(cur_symtb == nullptr);
	cur_symtb = new HSymbolTable;
	ctx->scope = cur_symtb;
	HSymbolTable::root = cur_symtb;

	visitChildren( ctx );

	//std::cout << "enter rule [Comp_unit]!" << std::endl;
	if (opts.no_check_main == 0 && functable.search_func("main") == SIZE_MAX)
		throw SemanticException("no main() function is found", ctx->stop->getLine(),
			ctx->stop->getCharPositionInLine(), ctx->stop->getText());	// EOF
	return nullptr;
}


std::any HAnalysis::visitDecl(CACTParser::DeclContext *ctx)
{
	CHECK_EXCEPTION(ctx);

	visitChildren( ctx );
	return nullptr;
}

std::any HAnalysis::visitConst_decl(CACTParser::Const_declContext *ctx)
{
	CHECK_EXCEPTION(ctx);

	for (auto def : ctx->const_def())
		ctx->b_type()->pass_to.push_back(&def->ctype.base_type);

	visitChildren( ctx );

	return nullptr;
}

std::any HAnalysis::visitB_type(CACTParser::B_typeContext *ctx)
{
	CHECK_EXCEPTION(ctx);

	Btype tp;

	visitChildren( ctx );

	tp = get_b_type(ctx);
	assert(tp != Btype::UNKNOWN);
	// an example to deal with inherit attribute
	for (auto *pt : ctx->pass_to)
	{
		assert(pt != nullptr);
		*pt = tp;
	}
	return nullptr;
}

std::any HAnalysis::visitConst_def(CACTParser::Const_defContext *ctx)
{
	CHECK_EXCEPTION(ctx);

	visitChildren( ctx );

	auto civ = ctx->const_init_val();
	size_t line_no = 0U, pos_in_line = 0U;
	if (ctx->start != nullptr)
	{
		line_no = ctx->start->getLine();
		pos_in_line = ctx->start->getCharPositionInLine();
	}

	ctx->ctype.dim.clear();
	ctx->ctype.is_const = true;
	// `base_type` has been assigned in `visitB_type()`
	for (auto ic : ctx->Int_const())
	{	// we assume that the order is from left to right
		long nd;
		try {
			nd = get_int_const(ic->getText());
			if (nd <= 0L || nd > CACT_Max_array_size)
				throw std::out_of_range("size 0 or too large");
		}
		catch (std::out_of_range &oe)
		{
			throw SemanticException("size 0 or too large", line_no,
				pos_in_line, ic->getText());
		}
		ctx->ctype.dim.push_back((uint32_t)nd);
	}

	if (!is_valid_init_val(ctx->ctype, civ->ctype))
		throw SemanticException("unexpected type", line_no,
			pos_in_line, civ->getText());
	/*
	 * `civ->ctype.base_type` being `VOID` (`{ }`) will cause
	 * `get_data_of_const_init_val()` fail, so we set it.
	 */
	if (civ->ctype.base_type == Btype::VOID)
		civ->ctype.base_type = ctx->ctype.base_type;

	size_t hsteidx;
	const void *init_data;

	if ((hsteidx = cur_symtb->add_symbol(ctx->ID()->getText(), &ctx->ctype))
		== SIZE_MAX)
		throw SemanticException("variable redefined", line_no,
			pos_in_line, ctx->ID()->getText());
	
	init_data = get_data_of_const_init_val(civ, ctx->ctype.dim);

	if (cur_symtb->parent() == nullptr)
		// the global symbol table
		assert(hxd1_ir.add_extvar(cur_symtb->symbol(hsteidx), init_data)
			!= SIZE_MAX);
	else
	{	// for local const variable
		hxd1_ir.generate_init_code(cur_symtb, hsteidx, init_data);
	}
	return nullptr;
}

std::any HAnalysis::visitConst_init_val(CACTParser::Const_init_valContext *ctx)
{
	CHECK_EXCEPTION(ctx);

	visitChildren( ctx );
	ctx->ctype.is_const = true;
	// an example to deal with synthesized attribute
	ctx->ctype.dim.clear();
	if (ctx->const_expr() != nullptr)
	{
		ctx->ctype.base_type = ctx->const_expr()->type;
		// use empty vector to represent scalar
	}
	else
	{
		Btype bt = Btype::VOID;
		size_t ddepth = 0UL;
		bool ddepth_is_set = false;
		// use VOID to represent common type
		/*
		 * val "{ }" has dimension {0}, base type 'VOID'.
		 * This situation (`ctx->const_init_val().size()` is 0) can also
		 * be dealt with by the algorithm below.
		 */
		for (auto civ : ctx->const_init_val())
		{
			// Types of element in an array must be coherent or `VOID`
			if (civ->ctype.base_type != Btype::VOID)
			{
				if (bt == Btype::VOID)
					bt = civ->ctype.base_type;
				if (bt != Btype::VOID && bt != civ->ctype.base_type)
					throw SemanticException("various types", ctx->start->getLine(),
						ctx->start->getCharPositionInLine(), ctx->getText());
			}
			// Depths (size of `ctype.dim`) must be coherent
			if (!ddepth_is_set)
			{
				ddepth = civ->ctype.dim.size();
				ddepth_is_set = true;
			}
			else
				if (ddepth != civ->ctype.dim.size())
					throw SemanticException("various dimension", ctx->start->getLine(),
						ctx->start->getCharPositionInLine(), ctx->getText());
		}
		// determine the dimension of `ctx` (current node)
		for (size_t i = 0U; i < ddepth; ++i)
			ctx->ctype.dim.push_back(0U);
		for (auto civ : ctx->const_init_val())
		{	/*
			 * max rule: for example, { {5, 0}, {3}, {3, 0, 5} } has dimension
			 * { 3, 3 }. The first 3 is the number of children, the second 3
			 * is the max of 2, 1, 3.
			 */
			for (size_t i = 0U; i < ddepth; ++i)
				if (civ->ctype.dim.at(i) > ctx->ctype.dim.at(i))
					ctx->ctype.dim.at(i) = civ->ctype.dim.at(i);
		}
		ctx->ctype.dim.insert(ctx->ctype.dim.begin(), ctx->const_init_val().size());
		ctx->ctype.base_type = bt;
	}
	return nullptr;
}

std::any HAnalysis::visitVar_decl(CACTParser::Var_declContext *ctx)
{
	CHECK_EXCEPTION(ctx);

	for (auto def : ctx->var_def())
		ctx->b_type()->pass_to.push_back(&def->vtype.base_type);

	visitChildren( ctx );
	return nullptr;
}

std::any HAnalysis::visitVar_def(CACTParser::Var_defContext *ctx)
{
	CHECK_EXCEPTION(ctx);

	visitChildren( ctx );

	auto civ = ctx->const_init_val();
	size_t line_no = 0U, pos_in_line = 0U;
	if (ctx->start != nullptr)
	{
		line_no = ctx->start->getLine();
		pos_in_line = ctx->start->getCharPositionInLine();
	}

	ctx->vtype.dim.clear();
	ctx->vtype.is_const = false;
	// `base_type` has been assigned in `visitB_type()`
	for (auto ic : ctx->Int_const())
	{	// we assume that the order is from left to right
		long nd;
		try {
			nd = get_int_const(ic->getText());
			if (nd <= 0L || nd > CACT_Max_array_size)
				throw std::out_of_range("size 0 or too large");
		}
		catch (std::out_of_range &oe)
		{
			throw SemanticException("size 0 or too large", line_no,
				pos_in_line, ic->getText());
		}
		ctx->vtype.dim.push_back((uint32_t)nd);
	}

	if (civ != nullptr && !is_valid_init_val(ctx->vtype, civ->ctype))
		throw SemanticException("unexpected type", line_no,
			pos_in_line, civ->getText());
	/*
	 * `civ->ctype.base_type` being `VOID` (`{ }`) will cause
	 * `get_data_of_const_init_val()` fail, so we set it.
	 */
	if (civ != nullptr && civ->ctype.base_type == Btype::VOID)
		civ->ctype.base_type = ctx->vtype.base_type;

	size_t hsteidx;
	const void *init_data;

	if ((hsteidx = cur_symtb->add_symbol(ctx->ID()->getText(), &ctx->vtype)) == SIZE_MAX)
		throw SemanticException("variable redefined", line_no,
			pos_in_line, ctx->ID()->getText());

	if (civ != nullptr)
		init_data = get_data_of_const_init_val(civ, ctx->vtype.dim,
			cur_symtb->parent() == nullptr);
	else
		init_data = nullptr;

	if (cur_symtb->parent() == nullptr)
		// the global symbol table
		assert(hxd1_ir.add_extvar(cur_symtb->symbol(hsteidx), init_data)
			!= SIZE_MAX);
	else
	{	// local variable
		/*
		 * For a local variable, `init_data` being `nullptr` means that
		 * it is not initialized.
		 */
		hxd1_ir.generate_init_code(cur_symtb, hsteidx, init_data);
	}
	return nullptr;
}

std::any HAnalysis::visitFunc_def(CACTParser::Func_defContext *ctx)
{
	CHECK_EXCEPTION(ctx);

	visitChildren( ctx );
	return nullptr;
}

std::any HAnalysis::visitFunc_type(CACTParser::Func_typeContext *ctx)
{
	CHECK_EXCEPTION(ctx);

	visitChildren( ctx );
	return nullptr;
}

std::any HAnalysis::visitFunc_params(CACTParser::Func_paramsContext *ctx)
{
	CHECK_EXCEPTION(ctx);

	visitChildren( ctx );
	return nullptr;
}

std::any HAnalysis::visitFunc_param(CACTParser::Func_paramContext *ctx)
{
	CHECK_EXCEPTION(ctx);

	size_t line_no = ctx->start->getLine(),
		pos_in_line = ctx->start->getCharPositionInLine();

	/*
	 * push `base_type` into `pass_to`so that it will be set
	 * in `visitB_type()` (during `visitChildren()`).
	 */
	ctx->b_type()->pass_to.push_back(&ctx->ptype.base_type);
	ctx->ptype.is_const = false;

	visitChildren( ctx );

	size_t pdepth = ctx->LSqr().size();
	if (ctx->Int_const().size() < pdepth)
		// parameter `int p[][5]` has `dim` `{0, 5}`
		ctx->ptype.dim.push_back(0U);
	for (auto ic : ctx->Int_const())
	{
		long nd;
		try {
			nd = get_int_const(ic->getText());
			if (nd <= 0L || nd > CACT_Max_array_size)
				throw std::out_of_range("size 0 or too large");
		}
		catch (std::out_of_range &oe)
		{
			throw SemanticException("size 0 or too large", line_no,
				pos_in_line, ic->getText());
		}
		ctx->ptype.dim.push_back((uint32_t)nd);
	}

	return nullptr;
}

std::any HAnalysis::visitBlock(CACTParser::BlockContext *ctx)
{
	CHECK_EXCEPTION(ctx);
	cur_symtb = cur_symtb->add_child();
	ctx->scope = cur_symtb;

	CACTParser::Func_defContext *func_def_ctx;
	std::vector<size_t> params;

	if ((func_def_ctx = dynamic_cast<CACTParser::Func_defContext *>(ctx->parent))
		!= nullptr)
	{	// If function definition is found, register it.
		Btype frettype;
		std::vector<const cact_type_t *> paramtype;
		//if (opts.debug_en != 0)
		//	std::cout << "func def found: " << func_def_ctx->ID()->getText() << std::endl;
		
		assert((frettype = get_f_type(func_def_ctx->func_type())) != Btype::UNKNOWN);
		cur_func_rtype = frettype;
		if (func_def_ctx->func_params() != nullptr)
			for (auto fp : func_def_ctx->func_params()->func_param())
			{
				// pointer to the symbol table entry of the parameter
				size_t hsteidx;
				// add parameters to the temporary list
				paramtype.push_back(&fp->ptype);
				// add parameters to symbol table
				hsteidx = ctx->scope->add_symbol(fp->ID()->getText(), &fp->ptype);
				if (hsteidx == SIZE_MAX)
					throw SemanticException("parameter redefined",
						fp->start->getLine(), fp->start->getCharPositionInLine(),
						fp->getText());
				params.push_back(hsteidx);
			}
		// register the function in function table
		func_def_ctx->hfteidx =
			functable.add_func(func_def_ctx->ID()->getText(), frettype,
				ctx->scope, paramtype);
		if (func_def_ctx->hfteidx == SIZE_MAX)
		{
			throw SemanticException("function redefined", func_def_ctx->start->getLine(),
				func_def_ctx->start->getCharPositionInLine(), func_def_ctx->ID()->getText());
		}
		/*
		 * Add the function to IR code table and enter it.
		 * This will cause parameters to be bound with temporaries automatically.
		 */
		hxd1_ir.enter_function(hxd1_ir.add_funct(functable.func(func_def_ctx->hfteidx),
			ctx->scope, params));

		if (opts.debug_level != 0)
			hxd1_ir.append_emptylines(1u);

		// check `main()` function
		if (opts.no_check_main == 0
			&& functable.func(func_def_ctx->hfteidx).funcid == "main")
		{
			if (functable.func(func_def_ctx->hfteidx).rettype != Btype::INT ||
				functable.func(func_def_ctx->hfteidx).paramtype.size() > 0U)
				throw SemanticException(
					"should have return type int and no parameter",
					func_def_ctx->start->getLine(),
					func_def_ctx->start->getCharPositionInLine(),
					func_def_ctx->ID()->getText()
				);
		}
	}

	visitChildren( ctx );

	cur_symtb = cur_symtb->parent();
	if (func_def_ctx != nullptr)
	{
		CACTParser::StmtContext *last_stmt = nullptr;
		for (auto bi : ctx->block_item())
			if (bi->stmt())
				last_stmt = bi->stmt();
		// check `return` statement
		if (last_stmt == nullptr || !is_terminated_with_return(last_stmt))
		{
			if (cur_func_rtype != Btype::VOID)
				throw SemanticException(
					"control reaches end of non-void function '" +
					func_def_ctx->ID()->getText() + '\'',
					ctx->RCur()->getSymbol()->getLine(),
					ctx->RCur()->getSymbol()->getCharPositionInLine(),
					ctx->RCur()->getText()
				);
			else	// append a `ret void` instruction
				hxd1_ir.append_ret(HIRBtype::VOID, Zero);
		}
		cur_func_rtype = Btype::UNKNOWN;
		hxd1_ir.exit_function();
	}
	return nullptr;
}

std::any HAnalysis::visitBlock_item(CACTParser::Block_itemContext *ctx)
{
	CHECK_EXCEPTION(ctx);

	visitChildren( ctx );
	return nullptr;
}

std::any HAnalysis::visitStmt(CACTParser::StmtContext *ctx)
{
	CHECK_EXCEPTION(ctx);
	const char *cominfo = nullptr;
	size_t line_no = ctx->start->getLine(),
		pos_in_line = ctx->start->getCharPositionInLine();

	if (ctx->AssignOp())
	{	// Assignment statement
		visitChildren( ctx );

		const cact_type_t &dst_type = ctx->lval()->hexpr->etype();
		const cact_type_t &src_type = ctx->expr()->hexpr->etype();
		auto dst = ctx->lval()->start;
		if (dst_type.is_const)
			throw SemanticException("cannot assign a const variable",
				dst->getLine(), dst->getCharPositionInLine(), ctx->lval()->getText());
		if (dst_type.dim.size() > 0U)
			throw SemanticException("cannot assign an array",
				dst->getLine(), dst->getCharPositionInLine(), ctx->lval()->getText());
		if (src_type.base_type != dst_type.base_type || src_type.dim.size() > 0U)
			throw SemanticException("assignment between different type is not allowed",
				dst->getLine(), dst->getCharPositionInLine(), ctx->getText());

		// generate IR code for assignment
		const HIRBtype btype = CACTBtype2HIRBtype(dst_type.base_type);
		HIROpnd lvaddr = hxd1_ir.generate_expr_code(ctx->lval()->hexpr, false,
			INVALID_LABEL, INVALID_LABEL, true),
			rval = hxd1_ir.generate_expr_code(ctx->expr()->hexpr);
		
		if (opts.debug_level != 0)
			cominfo = ctx->lval()->getText().c_str();

		if (lvaddr.tp == HIROpnd::TEMP &&
			hxd1_ir.temp(lvaddr.temp).type != HIRBtype::POINTER)
			// if the lvalue has been bound with a temp, just assign it
			hxd1_ir.append_arlo(HIROper::ADD,
				lvaddr.temp, btype, rval, Zero, cominfo);
		else	// otherwise a store is necessary
			hxd1_ir.append_store(btype, lvaddr, rval, cominfo);
	}
	else if (ctx->block())
	{	// A new block
		visitChildren( ctx );
	}
	else if (ctx->BREAK())
	{	// `break;`
		HIRLabel next = loop_stack_get_top_next();
		if (next == INVALID_LABEL)
			throw SemanticException("not in a loop", line_no, pos_in_line,
				ctx->BREAK()->getText());
		else
			hxd1_ir.append_br(next);
	}
	else if (ctx->CONTINUE())
	{	// `continue;`
		HIRLabel begin = loop_stack_get_top_begin();
		if (begin == INVALID_LABEL)
			throw SemanticException("not in a loop", line_no, pos_in_line,
				ctx->CONTINUE()->getText());
		else
			hxd1_ir.append_br(begin);
	}
	else if (ctx->RETURN())
	{	// `return`
		std::string str = ctx->RETURN()->getText() + ' ';
		visitChildren( ctx );

		if (ctx->expr())
			str += ctx->expr()->getText();
		str += ctx->Semicolon()->getText();
		assert(cur_func_rtype != Btype::UNKNOWN);
	
		if ((ctx->expr() == nullptr && cur_func_rtype != Btype::VOID) ||
			(ctx->expr() != nullptr &&
			(ctx->expr()->hexpr->etype().base_type != cur_func_rtype ||
			ctx->expr()->hexpr->etype().dim.size() > 0U)))
			throw SemanticException("wrong return value type",
				line_no, pos_in_line, str);
		// generate IR code for `return`
		HIROpnd fret;
		if (opts.debug_level != 0)
			hxd1_ir.append_emptylines(1u);
		if (ctx->expr() == nullptr)
			// `fret` here is meaningless
			hxd1_ir.append_ret(HIRBtype::VOID, fret);
		else
		{
			fret = hxd1_ir.generate_expr_code(ctx->expr()->hexpr);
			hxd1_ir.append_ret(CACTBtype2HIRBtype(cur_func_rtype), fret);
		}
		if (opts.debug_level != 0)
			hxd1_ir.append_emptylines(1u);
	}
	else if (ctx->IF() != nullptr || ctx->WHILE() != nullptr)
	{	// `if`
		auto cond = ctx->expr();
		size_t line_no = cond->start->getLine(),
			pos_in_line = cond->start->getCharPositionInLine();
		
		HIRLabel lbtrue = hxd1_ir.new_temp(HIRBtype::VOID),
			next = hxd1_ir.new_temp(HIRBtype::VOID),
			lbfalse;
		// visit child `expr` manually
		ctx->expr()->accept(this);

		if (!is_bool_scalar(ctx->expr()->hexpr->etype()))
			throw SemanticException("condition expression is not bool scalar",
				line_no, pos_in_line, cond->getText());

		if (ctx->IF())
		{	// `if`
			if (ctx->ELSE())
			{
				lbfalse = hxd1_ir.new_temp(HIRBtype::VOID);
				// visit child `expr` manually
				//ctx->expr()->accept(this);

				hxd1_ir.generate_expr_code(ctx->expr()->hexpr, true, lbtrue, lbfalse);
				hxd1_ir.append_label(lbtrue);
				// visit the first `stmt` (in true branch)
				ctx->stmt(0ul)->accept(this);

				hxd1_ir.append_br(next);
				hxd1_ir.append_label(lbfalse);
				// visit the second `stmt` (in false branch)
				ctx->stmt(1ul)->accept(this);
			}
			else
			{
				// visit child `expr` manually
				ctx->expr()->accept(this);

				hxd1_ir.generate_expr_code(ctx->expr()->hexpr, true, lbtrue, next);
				hxd1_ir.append_label(lbtrue);
				// visit the first `stmt` (in true branch)
				ctx->stmt(0ul)->accept(this);
			}
			if (opts.debug_level != 0)
				hxd1_ir.append_emptylines(1u);
			hxd1_ir.append_label(next);
		}
		else
		{	// `while`

			HIRLabel &lbbegin = lbfalse;
			lbbegin = hxd1_ir.new_temp(HIRBtype::VOID);
			hxd1_ir.append_label(lbbegin);
			// visit child `expr` manually
			//ctx->expr()->accept(this);

			hxd1_ir.generate_expr_code(ctx->expr()->hexpr, true, lbtrue, next);
			hxd1_ir.append_label(lbtrue);

			enter_loop(lbbegin, next);
			// visit child `stmt` (loop body)
			ctx->stmt(0ul)->accept(this);
			exit_loop();

			hxd1_ir.append_br(lbbegin);
			if (opts.debug_level != 0)
				hxd1_ir.append_emptylines(1u);
			hxd1_ir.append_label(next);
		}
	}
	else if (ctx->expr())
	{	// expr `;`
		visitChildren( ctx );

		if (opts.debug_level != 0)
			hxd1_ir.append_emptylines(1u);
		hxd1_ir.generate_expr_code(ctx->expr()->hexpr);
		if (opts.debug_level != 0)
			hxd1_ir.append_emptylines(1u);
	}
	return nullptr;
}

std::any HAnalysis::visitExpr(CACTParser::ExprContext *ctx)
{
	CHECK_EXCEPTION(ctx);

	visitChildren( ctx );

	ctx->hexpr = ctx->lg_or_expr()->hexpr;
	warnings.locate(ctx);
	return nullptr;
}

std::any HAnalysis::visitConst_expr(CACTParser::Const_exprContext *ctx)
{
	CHECK_EXCEPTION(ctx);

	visitChildren( ctx );
	if (ctx->Bool_const() != nullptr)
		ctx->type = Btype::BOOL;
	else
	{
		auto pn = ctx->number();
		assert(pn != nullptr);
		if (pn->Int_const() != nullptr)
			ctx->type = Btype::INT;
		else if (pn->Float_const() != nullptr)
			ctx->type = Btype::FLOAT;
		else if (pn->Double_const() != nullptr)
			ctx->type = Btype::DOUBLE;
	}
	assert(ctx->type != Btype::UNKNOWN);
	return nullptr;
}

std::any HAnalysis::visitLval(CACTParser::LvalContext *ctx)
{
	CHECK_EXCEPTION(ctx);
	HSymbolTable *psymtb;

	//const HSTEntry *ps = cur_symtb->search_symbol(ctx->ID()->getText());
	size_t hsteidx = cur_symtb->search_symbol(ctx->ID()->getText(), psymtb);
	if (hsteidx == SIZE_MAX)
		throw SemanticException("undefined symbol", ctx->start->getLine(),
			ctx->start->getCharPositionInLine(), ctx->ID()->getText());

	visitChildren( ctx );

	try {
		ctx->hexpr = new HExpression(psymtb, hsteidx);
		for (auto idx : ctx->expr())
			// array indexes
			ctx->hexpr = new HExpression(HExpression::ARR, ctx->hexpr, idx->hexpr);
	}
	catch (const char *msg)
	{
		size_t line_no = ctx->start->getLine(),
			pos_in_line = ctx->start->getCharPositionInLine();
		throw SemanticException(msg, line_no, pos_in_line, ctx->getText());
	}
	return nullptr;
}

std::any HAnalysis::visitPrimary_expr(CACTParser::Primary_exprContext *ctx)
{
	CHECK_EXCEPTION(ctx);

	visitChildren( ctx );

	// `const_expr` is a leaf node in expression tree
	try {
		if (ctx->lval())
		{
			ctx->hexpr = ctx->lval()->hexpr;
		}
		else if (ctx->const_expr())
		{
			auto cstexpr = ctx->const_expr();
			if (cstexpr->Bool_const())
			{
				if (cstexpr->Bool_const()->getText() == "true")
					ctx->hexpr = new HExpression(true);
				else
					ctx->hexpr = new HExpression(false);
			}
			else
			{
				assert(cstexpr->number() != nullptr);
				const number_type_t &val = cstexpr->number()->val;
				switch (val.type)
				{
				case Btype::INT:
					ctx->hexpr = new HExpression(val.ival);
					break;
				case Btype::FLOAT:
					ctx->hexpr = new HExpression(val.fval);
					break;
				case Btype::DOUBLE:
					ctx->hexpr = new HExpression(val.dval);
					break;
				default:
					assert(0);
				}
			}
		}
		else
		{
			assert(ctx->expr() != nullptr);
			ctx->hexpr = ctx->expr()->hexpr;
		}
	}
	catch (const char *msg)
	{
		size_t line_no = ctx->start->getLine(),
			pos_in_line = ctx->start->getCharPositionInLine();
		throw SemanticException(msg, line_no, pos_in_line, ctx->getText());
	}
	return nullptr;
}

std::any HAnalysis::visitNumber(CACTParser::NumberContext *ctx)
{
	CHECK_EXCEPTION(ctx);

	visitChildren( ctx );

	const std::string str = ctx->getText();
	size_t line_no = 0U, pos_in_line = 0U;
	if (ctx->start != nullptr)
	{
		line_no = ctx->start->getLine();
		pos_in_line = ctx->start->getCharPositionInLine();
	}

	bool neg = false;
	// determine the base type and calc the value
	try {
		if (ctx->AddOp() != nullptr && ctx->AddOp()->getText() == "-")
			neg = true;
		if (ctx->Int_const())
		{
			ctx->val.type = Btype::INT;
			if (ctx->AddOp() != nullptr)
				ctx->val.ival = get_int_const(str.substr(1U));
			else
				ctx->val.ival = get_int_const(str);
			if (neg)
				ctx->val.ival = -ctx->val.ival;
			if (ctx->val.ival > INT32_MAX || ctx->val.ival < INT32_MIN)
			{
				ctx->val.ival = int32_t(ctx->val.ival);
				throw std::out_of_range("int overflow");
			}
		}
		else if (ctx->Float_const())
		{
			ctx->val.type = Btype::FLOAT;
			ctx->val.fval = std::stof(str);
			// `str` already includes signal
			//if (neg)
			//	ctx->val.fval = -ctx->val.fval;
		}
		else
		{
			assert(ctx->Double_const() != nullptr);
			ctx->val.type = Btype::DOUBLE;
			ctx->val.dval = std::stod(str);
			// `str` already includes signal
			//if (neg)
			//	ctx->val.dval = -ctx->val.dval;
		}
	}
	catch (std::out_of_range &oe)
	{
		//throw SemanticException("out of range", line_no,
		//	pos_in_line, str);
		// If floating point overflows, fill it with Inf or 0.
		double d = std::atof(str.c_str());
		if (ctx->Float_const())
			ctx->val.fval = d;
		else if (ctx->Double_const())
			ctx->val.dval = d;
		warnings.add_warning("overflow of constant");
		warnings.locate(ctx);
	}
	catch (std::invalid_argument &ie)
	{	// can be caused by `++3`
		throw SyntaxException(std::string("invalid number '") + str + '\'',
			line_no, pos_in_line);
	}
	catch (std::exception &ex)
	{
		throw std::runtime_error(std::string("runtime error in visitNumber() for ") +
			"line " + std::to_string(line_no) + ':' +
			std::to_string(pos_in_line) + ": '" + str + '\'');
	}

	return nullptr;
}

std::any HAnalysis::visitUnary_expr(CACTParser::Unary_exprContext *ctx)
{
	CHECK_EXCEPTION(ctx);

	visitChildren( ctx );

	try {
		if (ctx->primary_expr())
		{
			ctx->hexpr = ctx->primary_expr()->hexpr;
		}
		else if (ctx->unary_expr())
		{
			auto uexpr = ctx->unary_expr();
			if (ctx->NotSign())
				ctx->hexpr = !(*(uexpr->hexpr));
			else if (ctx->AddOp())
				ctx->hexpr = 
					ctx->AddOp()->getText() == "+" ? +*uexpr->hexpr : -*uexpr->hexpr;
			else
				assert(0);
		}
		else
		{	// a function call
			assert(ctx->ID() != nullptr);
			const std::string &funcid = ctx->ID()->getText();
			size_t hfteidx = functable.search_func(funcid);
			size_t i = 0U, argc = (ctx->func_args() == nullptr
				? 0U : ctx->func_args()->expr().size());
			HExpression *pfa;

			if (hfteidx == SIZE_MAX)
				throw "undefined function";
			if (argc < functable.func(hfteidx).paramtype.size())
				throw "too few arguments";
			if (argc > functable.func(hfteidx).paramtype.size())
				throw "too many arguments";
			
			pfa = ctx->hexpr = new HExpression(hfteidx);
			if (argc > 0U)
				while (pfa != nullptr)
				{
					if (!is_legal_func_arg(
						*(functable.func(hfteidx).paramtype[i]),
						ctx->func_args()->expr(i)->hexpr->etype()))
						throw "illegal function argument";
					assert (pfa->set_lchild(ctx->func_args()->expr(i)->hexpr) != nullptr);
					pfa = pfa->rchild();
					++i;
				}
		}
	}
	catch (const char *msg)
	{
		size_t line_no = ctx->start->getLine(),
			pos_in_line = ctx->start->getCharPositionInLine();
		throw SemanticException(msg, line_no, pos_in_line, ctx->getText());
	}
	return nullptr;
}

std::any HAnalysis::visitFunc_args(CACTParser::Func_argsContext *ctx)
{
	CHECK_EXCEPTION(ctx);

	visitChildren( ctx );
	return nullptr;
}

std::any HAnalysis::visitMul_expr(CACTParser::Mul_exprContext *ctx)
{
	CHECK_EXCEPTION(ctx);

	visitChildren( ctx );

	try {
		auto mexpr = ctx->mul_expr();
		auto uexpr = ctx->unary_expr();
		if (ctx->MulOp())
			ctx->hexpr = (
				ctx->MulOp()->getText() == "*" ? *mexpr->hexpr * *uexpr->hexpr :
				ctx->MulOp()->getText() == "/" ? *mexpr->hexpr / *uexpr->hexpr :
					*mexpr->hexpr % *uexpr->hexpr
				);
		else
			ctx->hexpr = uexpr->hexpr;
		// for division by zero warnings
		warnings.locate(ctx);
	}
	catch (const char *msg)
	{
		size_t line_no = ctx->start->getLine(),
			pos_in_line = ctx->start->getCharPositionInLine();
		throw SemanticException(msg, line_no, pos_in_line, ctx->getText());
	}
	return nullptr;
}

std::any HAnalysis::visitAdd_expr(CACTParser::Add_exprContext *ctx)
{
	CHECK_EXCEPTION(ctx);

	visitChildren( ctx );

	try {
		auto aexpr = ctx->add_expr();
		auto mexpr = ctx->mul_expr();
		if (ctx->AddOp())
			ctx->hexpr = (ctx->AddOp()->getText() == "+" ?
				*aexpr->hexpr + *mexpr->hexpr : *aexpr->hexpr - *mexpr->hexpr);
		else
			ctx->hexpr = mexpr->hexpr;
	}
	catch (const char *msg)
	{
		size_t line_no = ctx->start->getLine(),
			pos_in_line = ctx->start->getCharPositionInLine();
		throw SemanticException(msg, line_no, pos_in_line, ctx->getText());
	}
	return nullptr;
}

std::any HAnalysis::visitRel_expr(CACTParser::Rel_exprContext *ctx)
{
	CHECK_EXCEPTION(ctx);

	visitChildren( ctx );

	try {
		auto aexpr = ctx->add_expr();
		if (ctx->RelOp())
		{
			const std::string &opstr = ctx->RelOp()->getText();
			ctx->hexpr = (
				opstr == "<" ? *aexpr[0]->hexpr < *aexpr[1]->hexpr :
				opstr == "<=" ? *aexpr[0]->hexpr <= *aexpr[1]->hexpr :
				opstr == ">" ? *aexpr[0]->hexpr > *aexpr[1]->hexpr :
				opstr == ">=" ? *aexpr[0]->hexpr >= *aexpr[1]->hexpr :
					nullptr
			);
			assert(ctx->hexpr != nullptr);
		}
		else
			ctx->hexpr = aexpr[0]->hexpr;
	}
	catch (const char *msg)
	{
		size_t line_no = ctx->start->getLine(),
			pos_in_line = ctx->start->getCharPositionInLine();
		throw SemanticException(msg, line_no, pos_in_line, ctx->getText());
	}
	return nullptr;
}

std::any HAnalysis::visitEq_expr(CACTParser::Eq_exprContext *ctx)
{
	CHECK_EXCEPTION(ctx);

	visitChildren( ctx );

	try {
		auto eexpr = ctx->eq_expr();
		auto rexpr = ctx->rel_expr();
		if (ctx->EqOp())
			ctx->hexpr = (ctx->EqOp()->getText() == "==" ?
				*eexpr->hexpr == *rexpr->hexpr : *eexpr->hexpr != *rexpr->hexpr);
		else
			ctx->hexpr = rexpr->hexpr;
	}
	catch (const char *msg)
	{
		size_t line_no = ctx->start->getLine(),
			pos_in_line = ctx->start->getCharPositionInLine();
		throw SemanticException(msg, line_no, pos_in_line, ctx->getText());
	}
	return nullptr;
}

std::any HAnalysis::visitLg_and_expr(CACTParser::Lg_and_exprContext *ctx)
{
	CHECK_EXCEPTION(ctx);

	visitChildren( ctx );
	try {
		auto aexpr = ctx->lg_and_expr();
		auto eexpr = ctx->eq_expr();
		if (ctx->LgAndOp())
			ctx->hexpr = *aexpr->hexpr && *eexpr->hexpr;
		else
			ctx->hexpr = eexpr->hexpr;
	}
	catch (const char *msg)
	{
		size_t line_no = ctx->start->getLine(),
			pos_in_line = ctx->start->getCharPositionInLine();
		throw SemanticException(msg, line_no, pos_in_line, ctx->getText());
	}
	return nullptr;
}

std::any HAnalysis::visitLg_or_expr(CACTParser::Lg_or_exprContext *ctx)
{
	CHECK_EXCEPTION(ctx);

	visitChildren( ctx );

	try {
		auto oexpr = ctx->lg_or_expr();
		auto aexpr = ctx->lg_and_expr();
		if (ctx->LgOrOp())
			ctx->hexpr = *oexpr->hexpr || *aexpr->hexpr;
		else
			ctx->hexpr = aexpr->hexpr;
	}
	catch (const char *msg)
	{
		size_t line_no = ctx->start->getLine(),
			pos_in_line = ctx->start->getCharPositionInLine();
		throw SemanticException(msg, line_no, pos_in_line, ctx->getText());
	}
	return nullptr;
}

std::any HAnalysis::visitErrorNode(tree::ErrorNode * node)
{
	if (opts.debug_level != 0)
		std::cerr << "visit error node!" << std::endl;
	throw SyntaxException();
	error_code = int(HECode::STXERR);
	return nullptr;
}
